#include <string.h>

#ifdef NATIVE_SOCKET
#include <sys/socket.h>
#include <fcntl.h>
#endif

#include "tcp_endpoint.h"
#include "net_context.h"
#include "tcp_connection.h"

using namespace usnet;

int TCPEndpoint::Accept()
{
    auto ctx = NetContext::GetContext();
    if ((tcp_listener_ == nullptr) && conn_tab_.size()) {
        LOG_ERROR(ctx, "local endpoint has been connected");
        return -1;
    }
    if (tcp_listener_ == nullptr) {
        tcp_listener_ = TCPListener::MakeUnique(&addr_);
        if (tcp_listener_ == nullptr) {
            LOG_ERROR(ctx, "failed to create TCPListener instance");
            return -1;
        }
    }
    auto&& conn = tcp_listener_->Accept();
    if (conn == nullptr) {
        return -1;
    }
    const int fd = conn->GetConnFD();
    conn_tab_[fd] = std::move(conn);
    return fd;
}

#ifndef NATIVE_SOCKET
int TCPEndpoint::Connect(const BaseEndpoint* rep)
{
    auto ctx = NetContext::GetContext();
    if (rep == nullptr) {
        LOG_ERROR(ctx, "invalid arguement: remote endpoint pointer is null");
        return -1;
    }
    if ((tcp_listener_) || (conn_tab_.size())) {
        LOG_ERROR(ctx, "local endpoint connected or listened");
        return -1;
    }
    auto raddr = rep->GetEndpointAddr();
    if (addr_.inet_proto_ != raddr.inet_proto_) {
        LOG_ERROR(ctx, "Internet Protocols dismatch");
        return -1;
    }
    auto conn = TCPConnection::MakeUnique(&addr_, raddr);
    if (conn == nullptr) {
        LOG_ERROR(ctx, "failed to create TCP TCPConnection instance");
        return -1;
    }
    const int fd = conn->GetConnFD();
    conn_tab_[fd] = std::move(conn);
    return fd;
}
#else
int TCPEndpoint::Connect(const BaseEndpoint* rep)
{
    auto ctx = NetContext::GetContext();
    if (rep == nullptr) {
        LOG_ERROR(ctx, "invalid arguement: remote endpoint pointer is null");
        return -1;
    }
    if ((tcp_listener_) || (conn_tab_.size())) {
        LOG_ERROR(ctx, "local endpoint connected or listened");
        return -1;
    }
    auto raddr = rep->GetEndpointAddr();
    if (addr_.inet_proto_ != raddr.inet_proto_) {
        LOG_ERROR(ctx, "Internet Protocols dismatch");
        return -1;
    }
    int sockfd { -1 };
    if (addr_.inet_proto_ == InetProto::IPv4) {
        struct sockaddr_in server_addr;
        memset(&server_addr, 0, sizeof(struct sockaddr_in));
        server_addr.sin_addr.s_addr = raddr.be_ip_.v4_;
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = raddr.be_port_;
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0) {
            LOG_ERROR(ctx, "socket() failed");
            return -1;
        }
        if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in)) < 0) {
            LOG_ERROR(ctx, "connect() failed");
            return -1;
        }
    }
    if (addr_.inet_proto_ == InetProto::IPv6) {
        struct sockaddr_in6 server_addr;
        memset(&server_addr, 0, sizeof(struct sockaddr_in6));
        memcpy(&server_addr.sin6_addr.s6_addr, raddr.be_ip_.v6_, sizeof(raddr.be_ip_.v6_));
        server_addr.sin6_family = AF_INET6;
        server_addr.sin6_port = raddr.be_port_;
        int sockfd = socket(AF_INET6, SOCK_STREAM, 0);
        if (sockfd < 0) {
            LOG_ERROR(ctx, "socket() failed");
            return -1;
        }
        if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in6)) < 0) {
            LOG_ERROR(ctx, "connect() failed");
            return -1;
        }
    }
    int flags = fcntl(sockfd, F_GETFL, 0);
    fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
    auto conn = TCPConnection::MakeUnique(&addr_, raddr, sockfd);
    if (conn == nullptr) {
        LOG_ERROR(ctx, "failed to create TCP TCPConnection instance");
        return -1;
    }
    const int fd = conn->GetConnFD();
    conn_tab_[fd] = std::move(conn);
    return fd;
}
#endif